package com.shockweb.common.calculate;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.shockweb.common.calculate.api.IParameter;
import com.shockweb.common.calculate.parse.ExpressionNode;
import com.shockweb.common.calculate.parse.ExpressionParse;
import com.shockweb.common.calculate.util.SimpleCalculate;

/**
 * 计算表达式
 * 按照用户自定义计算表达式进行计算，表达式的运算全部是以BigDecimal为基础保证了运算的精度无损失
 * 同时支持加减乘除、括号、逻辑运算、条件判断、正玄函数、余玄函数、绝对值、平方、开方、幂运算、对数、最大值、最小值等函数
 * 除此之外表达式中还支持变量，也允许开发人员为表达式计算开发新的函数。默认支持的函数如下：<br>
 * 绝对值：abs(x)、反余玄：acos(x)、反正玄：asin(x)、反正切：atan(x)、
 * 余玄cos(x)、条件判断：iif(x,y,z)、e为底的对数：log(x)、10为底的对数：log10(x)、
 * 最大值：max(x,y)、最小值：acos(x)、幂运算：pow(x,y)、随机数：random()、
 * 正玄sin(x)、平方：sqrt(x)、平方根：square(x)、正切：tan(x)
 * 
 * @author pengminghua
 */
public class Calculator {

	private HashMap<String,Object> resultCache = new HashMap<String,Object>();
	public ExpressionNode node = null;
	private HashMap<String,Object> params = new HashMap<String,Object>();
	
	/**
	 * 计算表达式
	 * @param expression
	 * @throws CalculateException
	 */
	public Calculator(String expression) throws CalculateException{
		init(expression,null,2,false,false);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param parameter
	 * @throws CalculateException
	 */
	public Calculator(String expression,IParameter parameter) throws CalculateException{
		init(expression,parameter,2,false,false);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param option
	 * @throws CalculateException
	 */
	public Calculator(String expression,boolean option) throws CalculateException{
		init(expression,null,2,option,option);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param parameter
	 * @param option
	 * @throws CalculateException
	 */
	public Calculator(String expression,IParameter parameter,boolean option) throws CalculateException{
		init(expression,parameter,2,option,option);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param divScale
	 * @throws CalculateException
	 */
	public Calculator(String expression,int divScale) throws CalculateException{
		init(expression,null,divScale,false,false);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param parameter
	 * @param divScale
	 * @throws CalculateException
	 */
	public Calculator(String expression,IParameter parameter,int divScale) throws CalculateException{
		init(expression,parameter,divScale,false,false);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param divScale
	 * @param option
	 * @throws CalculateException
	 */
	public Calculator(String expression,int divScale,boolean option) throws CalculateException{
		init(expression,null,divScale,option,option);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param parameter
	 * @param divScale
	 * @param option
	 * @throws CalculateException
	 */
	public Calculator(String expression,IParameter parameter,int divScale,boolean option) throws CalculateException{
		init(expression,parameter,divScale,option,option);
	}
	
	/**
	 * 计算表达式
	 * @param expression
	 * @param parameter
	 * @param divScale
	 * @param parse
	 * @param foreclose
	 * @throws CalculateException
	 */
	public Calculator(String expression,IParameter parameter,int divScale,boolean parse,boolean foreclose) throws CalculateException{
		init(expression,parameter,divScale,parse,foreclose);
	}
	
	/**
	 * 初始化
	 * @param expression
	 * @param parameter
	 * @param divScale
	 * @param parse
	 * @param foreclose
	 * @throws CalculateException
	 */
	private void init(String expression,IParameter parameter,int divScale,boolean parse,boolean foreclose) throws CalculateException{
		try {
			node = ExpressionParse.parse(expression,parameter,divScale,parse);
			if(foreclose) 
				compiler(node);
		} catch (CalculateParseException e) {
			throw new CalculateException(e);
		}
	}

	/**
	 * 清除所有参数
	 */
	public void clearParams(){
		params.clear();
	}
	
	/**
	 * 给表达式中的变量赋值
	 * @param key
	 * @param value
	 */
	public void putParam(String key,Object value){
		if(value!=null){
			if(value instanceof Double){
				params.put(key,new BigDecimal((Double)value));
			}else if(value instanceof Float){
				params.put(key,new BigDecimal((Float)value));
			}else if(value instanceof Integer){
				params.put(key,new BigDecimal((Integer)value));
			}else if(value instanceof Long){
				params.put(key,new BigDecimal((Long)value));
			}else if(value instanceof BigDecimal){
				params.put(key,value);
			}else if(value instanceof BigInteger){
				params.put(key,new BigDecimal((BigInteger)value));
			}else if(value instanceof String){
				params.put(key,value);
			}else{
				params.put(key,new BigDecimal(value.toString()));
			}
		}
	}

	/**
	 * 执行表达式
	 * @return Object
	 * @throws CalculateException
	 */
	public Object eval() throws CalculateException{
		resultCache.clear();
		return eval(node);
	}
	
	/**
	 * 预计算表达式
	 * @param node
	 * @return Object
	 * @throws CalculateException
	 */
	private Object compiler(ExpressionNode node)throws CalculateException{
		try{
			node.setValue(null);
			List<Object> params = new ArrayList<Object>();
			boolean flag = true; 
			for(int i=0;i<node.getNodeSize();i++){
				Object value = null;
				try{
					value = compiler(node.getNode(i));
				}catch(NumberFormatException e){
				}
				params.add(value);
				if(value==null)
					flag = false;
			}
			if(flag){
				SimpleCalculate sc = node.getSimpleCalculate();
				if(sc!=null){
					for(int i=0;i<params.size();i++){
						this.params.put("_" + i + "_",params.get(i));
					}
					sc.setParams(this.params);
					node.setValue(sc.eval());
				}else{
					node.setValue(node.getFunction().eval(params));
				}
			}
		}catch(CalculateException e){
			throw e;
		}catch(NumberFormatException e){
			
		}catch(Exception e){
			throw new CalculateException("Calculate '"+ node.getKey() + "' error",e);
		}
		return node.getValue();
	}
	
	/**
	 * 计算表达式
	 * @param node
	 * @return BigDecimal
	 * @throws CalculateException
	 */
	private Object eval(ExpressionNode node) throws CalculateException{
		try{
			String key = node.getKey();
			Object rtn = node.getValue()!=null?node.getValue():resultCache.get(key);
			if(rtn==null){
				List<Object> params = new ArrayList<Object>();
				for(int i=0;i<node.getNodeSize();i++){
					params.add(eval(node.getNode(i)));
				}
				SimpleCalculate sc = node.getSimpleCalculate();
				if(sc!=null){
					for(int i=0;i<params.size();i++){
						this.params.put("_" + i + "_",params.get(i));
					}
					sc.setParams(this.params);
					rtn = sc.eval();
				}else{
					rtn = node.getFunction().eval(params);
				}
				resultCache.put(key,rtn);
			}
			return rtn;
		}catch(CalculateException e){
			throw e;
		}catch(Exception e){
			throw new CalculateException("Calculate '"+ node.getKey() + "' error",e);
		}		
	}
}
